var util = require("util");
var slice = require("stream-slice").slice;
var Stream = require("stream");
const fs = require("fs");

const stat = util.promisify(fs.stat);
module.exports = async function (ctx, next) {
    var range = ctx.header.range;
    ctx.set("Accept-Ranges", "bytes");

    if (!range) {
        return next();
    }

    var ranges = rangeParse(range);

    if (!ranges || ranges.length == 0) {
        ctx.status = 416;
        return;
    }

    if (ctx.method == "PUT") {
        ctx.status = 400;
        return;
    }

    await next();
    if (ctx.response.has("Content-Range")) {
        return;
    }
    if (ctx.method != "GET" || ctx.body == null) {
        return;
    }

    var first = ranges[0];
    var rawBody = ctx.body;
    var len = rawBody.length;

    // avoid multi ranges
    var firstRange = ranges[0];
    var start = firstRange[0];
    var end = firstRange[1];

    if (!Buffer.isBuffer(rawBody)) {
        if (rawBody instanceof Stream.Readable) {
            len = ctx.length || "*";

            if (rawBody.path) {
                let path = rawBody.path;
                if (!Number.isInteger(len)) {
                    let stats = await stat(path);

                    len = stats.size;
                }
                rawBody = fs.createReadStream(rawBody.path, {
                    start,
                    end: end,
                });
            } else {
                if (end === Infinity && !Number.isInteger(len)) {
                    return;
                }
                rawBody = rawBody.pipe(slice(start, end + 1));
            }
        } else if (typeof rawBody !== "string") {
            rawBody = new Buffer(JSON.stringify(rawBody));
            len = rawBody.length;
        } else {
            rawBody = new Buffer(rawBody);
            len = rawBody.length;
        }
    }

    // Adjust infinite end
    if (end === Infinity) {
        if (Number.isInteger(len)) {
            end = len - 1;
        } else {
            // FIXME(Calle Svensson): If we don't know how much we can return, we do a normal HTTP 200 repsonse
            ctx.status = 200;
            return;
        }
    }

    ctx.status = 206;
    // Adjust end while larger than len
    if (Number.isInteger(len) && end >= len) {
        end = len - 1;
        if (start === 0) {
            ctx.status = 200;
        }
    }

    var args = [start, end + 1].filter(function (item) {
        return typeof item == "number";
    });

    ctx.set("Content-Range", rangeContentGenerator(start, end, len));

    if (rawBody instanceof Stream) {
        ctx.body = rawBody;
    } else {
        ctx.body = rawBody.slice.apply(rawBody, args);
    }

    if (len !== "*") {
        let ctxlength = end - start + 1;
        if (ctxlength <= 0) {
            ctx.status = 416;
        }

        ctx.length = Math.max(0, ctxlength);
    }
};

function rangeParse(str) {
    var token = str.split("=");
    if (!token || token.length != 2 || token[0] != "bytes") {
        return null;
    }
    return token[1]
        .split(",")
        .map(function (range) {
            return range.split("-").map(function (value) {
                if (value === "") {
                    return Infinity;
                }
                return Number(value);
            });
        })
        .filter(function (range) {
            return !isNaN(range[0]) && !isNaN(range[1]) && range[0] <= range[1];
        });
}

function rangeContentGenerator(start, end, length) {
    return util.format("bytes %d-%d/%s", start, end, length);
}
